#ifndef BTL_IMAGE_IMAGEPYRAMID
#define BTL_IMAGE_IMAGEPYRAMID

#include "Image.hpp"
#include "PixelTypes.hpp"

#include <Eigen/Dense>
#include <vector>
#include <cmath>

namespace btl
{
namespace image
{

/// Manages an image pyramid where each level is half the size
/// (a quarter the area) of the previous level.
///
/// No write access is provided to the image data, because that would
/// either lead to inconsistency in the pyramid, or the requirement for
/// some method of automatically updating the other pyramid layers, and
/// the policy for doing that should really be decided by the client
/// code, not by ImagePyramid.
template <class PixelType>
class ImagePyramid
{
   public:
      typedef ImageRegion<PixelType> image_region;
      typedef ImageRegion<const PixelType> const_image_region;

      ImagePyramid();
      ~ImagePyramid();

      bool null() const;
      int width() const;
      int height() const;
      int numLevels() const;

      void set(const const_image_region& base, int num_levels);
      void clear();

      /// Const access to image data
      const const_image_region& operator[](int level) const;

      /// Given a pixel coordinate at a specified level,
      /// calculates the floating point image coordinates at the base
      /// level, where integer values are at the corners of pixels
      /// (i.e., (0.0f, 0.0f) is the top left corner of the top left pixel)
      ///
      /// This is the inverse of the pixelCoords() transform.
      Eigen::Vector2f imageCoords(int level, const Eigen::Vector2i& pt) const;

      /// Calculates the coordinates of the pixel at the specified level
      /// that contains the given (base level) image coordinates.
      ///
      /// This is the inverse of the imageCoords() transform.
      Eigen::Vector2i pixelCoords(int level, const Eigen::Vector2f& x) const;
   private:
      typedef Image<PixelType> image_type;
      std::vector<image_type> _levels;
};

typedef ImagePyramid<PixelGrey> ImagePyramidGrey;
typedef ImagePyramid<PixelRGB> ImagePyramidRGB;
typedef ImagePyramid<PixelFloat> ImagePyramidFloat;

} // namespace image
} // namespace btl

namespace btl
{

using image::ImagePyramid;
using image::ImagePyramidGrey;
using image::ImagePyramidRGB;
using image::ImagePyramidFloat;

}

// ====================================================================
// === Implementation

namespace btl
{
namespace image
{

// helper functions
// defined as overloads rather than through templates,
// because I don't want the implementation in the header
void halfScaleImage(const ImageRegion<const PixelGrey>& in, ImageRegion<PixelGrey>& out);
void halfScaleImage(const ImageRegion<const PixelRGB>& in, ImageRegion<PixelRGB>& out);
void halfScaleImage(const ImageRegion<const PixelFloat>& in, ImageRegion<PixelFloat>& out);

template <class PixelType>
inline ImagePyramid<PixelType>::ImagePyramid() {}

template <class PixelType>
inline ImagePyramid<PixelType>::~ImagePyramid() {}

template <class PixelType>
inline bool ImagePyramid<PixelType>::null() const
{
   return this->_levels.empty();
}

template <class PixelType>
inline int ImagePyramid<PixelType>::width() const
{
   return null() ? 0 : this->_levels[0].width();
}

template <class PixelType>
inline int ImagePyramid<PixelType>::height() const
{
   return null() ? 0 : this->_levels[0].height();
}

template <class PixelType>
inline int ImagePyramid<PixelType>::numLevels() const
{
   return static_cast<int>(this->_levels.size());
}

template <class PixelType>
inline void ImagePyramid<PixelType>::set(
   const const_image_region& base, int num_levels)
{
   // need to create at least 1 level
   assert(num_levels > 0);
   // check we haven't been asked to create too many levels
   assert(num_levels == 1 || (base.width() >= ((1 << (num_levels - 2)) + 1)));
   assert(num_levels == 1 || (base.height() >= ((1 << (num_levels - 2)) + 1)));

   this->_levels.resize(num_levels);
   this->_levels[0].clone(base);
   for(int lvl = 1; lvl < num_levels; ++lvl)
   {
      const int w = (this->_levels[lvl - 1].width() + 1) / 2;
      const int h = (this->_levels[lvl - 1].height() + 1) / 2;
      this->_levels[lvl].allocate(w, h);
      halfScaleImage(this->_levels[lvl - 1], this->_levels[lvl]);
   }
}

template <class PixelType>
inline void ImagePyramid<PixelType>::clear()
{
   this->_levels.clear();
}

/// Const access to image data
template <class PixelType>
inline const typename ImagePyramid<PixelType>::const_image_region&
ImagePyramid<PixelType>::operator[](int level) const
{
   assert(level >= 0);
   assert(level < static_cast<int>(this->_levels.size()));

   return this->_levels[level];
}

template <class PixelType>
inline Eigen::Vector2f ImagePyramid<PixelType>::imageCoords(
   int level, const Eigen::Vector2i& pt) const
{

   float scale;
   scale = std::ldexp(1.0f, level); // scale = 2**level

   Eigen::Vector2f x;
   x(0) = (static_cast<float>(pt(0)) + 0.5f) * scale;
   x(1) = (static_cast<float>(pt(1)) + 0.5f) * scale;
   return x;
}

template <class PixelType>
inline Eigen::Vector2i ImagePyramid<PixelType>::pixelCoords(
   int level, const Eigen::Vector2f& x) const
{

   float scale;
   scale = std::ldexp(1.0f, -level); // scale = 2**(-level)

   Eigen::Vector2i pt;
   // conversion to int will floor the value
   pt(0) = static_cast<int>(x(0) * scale);
   pt(1) = static_cast<int>(x(1) * scale);
   return pt;
}

} // namespace image
} // namespace btl

#endif
